/**
 * @file comsock.c
 * @brief Implementazione della libreria comsock
 * @author Marco Luisi
 * Si dichiara che il contenuto di questo file e' in ogni sua parte opera originale dell' autore.
 */

#include <fnmatch.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>

#include "biblio.h"
#include "comsock.h"

/** Crea una socket AF_UNIX
 *  \param  path pathname della socket
 *
 *  \retval s    il file descriptor della socket  (s>0)
 *  \retval -1   in altri casi di errore (setta errno)
 *               errno = E2BIG se il nome eccede UNIX_PATH_MAX
 *
 *  in caso di errore ripristina la situazione inziale: rimuove eventuali socket create e chiude eventuali file descriptor rimasti aperti
 */
int createServerChannel(char* path) {
	int channel = 0;
	struct sockaddr_un sock_a;
	/* Controllo correttezza del path */
	if (!path) {
		errno = EINVAL;
		return (-1);
	}
	if(fnmatch("./tmp/*.sck",path,FNM_PATHNAME) != 0){
		return(-1);
	}
	if (strlen(path) >= UNIX_PATH_MAX) {
		errno = E2BIG;
		return (-1);
	}
	sock_a.sun_family = AF_UNIX;
	strncpy(sock_a.sun_path, path, UNIX_PATH_MAX);
	channel = socket(AF_UNIX, SOCK_STREAM, 0);
	EC_M1(channel);
	EC_M1(bind(channel,(struct sockaddr *)&sock_a, sizeof(sock_a)));
	EC_M1(listen(channel,SOMAXCONN));
	return (channel);
}

/** Chiude un canale lato server (rimuovendo la socket dal file system)
 *   \param path path della socket
 *   \param s file descriptor della socket
 *
 *   \retval 0  se tutto ok,
 *   \retval -1  se errore (setta errno)
 */
int closeServerChannel(char* path, int s) {
	unlink(path);
	return (close(s));
}

/** accetta una connessione da parte di un client
 *  \param  s socket su cui ci mettiamo in attesa di accettare la connessione
 *
 *  \retval  c il descrittore della socket su cui siamo connessi
 *  \retval  -1 in casi di errore (setta errno)
 */
int acceptConnection(int s) {
	return (accept(s, NULL, 0));
}

/** legge un messaggio dalla socket --- attenzione si richiede che il messaggio sia adeguatamente spacchettato e trasferito nella struttura msg
 *  \param  sc  file descriptor della socket
 *  \param msg  indirizzo della struttura che conterra' il messagio letto
 *		(deve essere allocata all'esterno della funzione)
 *
 *  \retval lung  lunghezza del buffer letto, se OK
 *  \retval  -1   in caso di errore (setta errno)
 *                 errno = ENOTCONN se il peer ha chiuso la connessione
 *                   (non ci sono piu' scrittori sulla socket)
 *
 */
int receiveMessage(int sc, message_t * msg) {
	unsigned int bytes = 0;
	/*Controllo del tipo del messaggio*/
	bytes = read(sc, &(msg->type), sizeof(char));
	if (bytes == 0) {
		errno = ENOTCONN;
		return (-1);
	}
	if (bytes == -1) {
		return (-1);
	}
	/*Lettura della lunghezza del messaggio*/
	bytes = 0;
	while ((bytes += read(sc, &(msg->length), sizeof(unsigned int)))
			< sizeof(unsigned int)) {
		if (bytes == 0) { //ATTENZIONE QUI lo zero potrebbe far saltare?!
			errno = ENOTCONN;
			return (-1); //QUI ERA SEOF OCCHIO!!!!!!!!!!!!!!
		}
		if (bytes == -1)
			return (-1);
	}

	if(msg->length > MAXBUF){
		errno = EMSGSIZE;
		return (-1);
	}

	/*Riempimento del buffer*/
	while ((bytes += read(sc, msg->buffer, msg->length)) < msg->length) {
		if (bytes == 0) {
			errno = ENOTCONN;
			return -1;
		}
		if (bytes == -1) {
			//ERRNO?
			return (-1);
		}
	}
	return (bytes);
}

/** scrive un messaggio sulla socket --- attenzione devono essere inviati SOLO i byte significativi del campo buffer (msg->length byte) --  si richiede che il messaggio venga scritto con un'unica write dopo averlo adeguatamente impacchettato
 *   \param  sc file descriptor della socket
 *   \param msg indirizzo della struttura che contiene il messaggio da scrivere
 *
 *   \retval  n    il numero di caratteri inviati (se scrittura OK)
 *   \retval -1   in caso di errore (setta errno)
 *                 errno = ENOTCONN se il peer ha chiuso la connessione
 *                   (non ci sono piu' lettori sulla socket)
 */
int sendMessage(int sc, message_t *msg) {
	int bytes = 0;
	int len = 0;
	char *sendbuffer;

	if (msg == NULL ) {
		errno = EINVAL;
		return -1;
	}

	len = sizeof(unsigned int) + sizeof(char) + msg->length;
	MALLOC_M1(sendbuffer,len);
	memcpy(sendbuffer,&(msg->type),sizeof(char));
	memcpy(&(sendbuffer[sizeof(char)]),&(msg->length),sizeof(unsigned int));
	memcpy(&(sendbuffer[sizeof(char)+sizeof(unsigned int)]),msg->buffer,msg->length);
	bytes = write(sc, sendbuffer, len);
	if (bytes < len) {
		return (-1);
	}
	free(sendbuffer);
	return bytes;
}


/** crea una connessione all socket del server. In caso di errore funzione ritenta ntrial volte la connessione (a distanza di k secondi l'una dall'altra) prima di ritornare errore.
 *   \param  path  nome del socket su cui il server accetta le connessioni
 *   \param  ntrial numeri di tentativi prima di restituire errore (ntrial <=MAXTRIAL)
 *   \param  k secondi l'uno dell'altro (k <=MAXSEC)
 *
 *   \return fd il file descriptor della connessione
 *            se la connessione ha successo
 *   \retval -1 in caso di errore (setta errno)
 *               errno = E2BIG se il nome eccede UNIX_PATH_MAX
 *
 *  in caso di errore ripristina la situazione inziale: rimuove eventuali socket create e chiude eventuali file descriptor rimasti aperti
 */
int openConnection(char* path, int ntrial, int k){

    int trials;
    int sc;
    struct sockaddr_un sa;
    if(path == NULL){
    	errno = EINVAL;
    	return(-1);
    }
	if(ntrial < 1 || ntrial > MAXTRIAL){
    	errno = EINVAL;
    	return(-1);
	}
	if(k < 1 || k > MAXSEC){
    	errno = EINVAL;
    	return(-1);
	}
    if(strlen(path) >= UNIX_PATH_MAX){
    	errno = E2BIG;
    	return(-1);
    }
	sa.sun_family = AF_UNIX;
    strncpy(sa.sun_path, path, UNIX_PATH_MAX);
    sc = socket(AF_UNIX, SOCK_STREAM, 0);
    EC_M1(sc);
    /* Si tenta la connessione al socket */
    for(trials = 0; trials < ntrial; trials++){
    /* Connect riuscita */
    if (connect(sc, (struct sockaddr *) &sa, sizeof(sa)) == 0){
    	return(sc);
    }
    sleep(k);
    }
    /*Impossibile stabilire una connessione al socket, errno settato da connect*/
    //GESTIONE SOCK DA CANCELLARE?!?!?!?!!!!!!!!!!!!!!
    return(-1);
}


/** Chiude una connessione
 *   \param s file descriptor della socket relativa alla connessione
 *
 *   \retval 0  se tutto ok,
 *   \retval -1  se errore (setta errno)
 */
int closeConnection(int s){
	return close(s);
}


/**
 * @brief Impostazione del messaggio da inviare
 *
 * @param msg messaggio da settare
 * @param string stringa per il buffer del messaggio
 * @param type tipo del messaggio
 * @return esito
 * @retval ERROR
 * @retval OK
 */
int message_setup(message_t *msg, char* string, char type){
	if (msg == NULL){
		return(ERROR);
	}
    memset(msg->buffer,'\0',MAXBUF);
    if (string == NULL) {
    	free(msg);
    	return(ERROR);
    }
	msg->type = type;
	msg->length = (unsigned int)strlen(string);
	strcpy(msg->buffer,string);
	return(OK);
}
